# -*- coding: utf-8 -*-

"""
京东配送回调SDK主要实现
"""

import json
import time
import hashlib
import socket
import logging
from typing import Dict, Any, Optional, Union
from urllib.parse import urlencode, urlparse
from decimal import Decimal

import requests

from .models import (
    Order, CallbackResult, ConnectivityResult, 
    DnsResult, PortResult, HttpResult, SDKConfig
)


class JingdongCallbackSDK:
    """
    京东配送回调SDK
    
    用于向海博技术服务平台发送京东配送订单状态变更、骑手信息变更、费用信息变更等回调
    """
    
    # 版本号
    VERSION = '1.0.0'
    
    # 海博技术服务平台回调接口地址
    CALLBACK_URL_TEST = 'https://pre-carrieropen.hiboos.com/api/delivery/statusCallback'
    CALLBACK_URL_SANDBOX = 'https://carrieropen-tool.hiboos.com/api/delivery/statusCallback'
    CALLBACK_URL_PROD = 'https://carrieropen.hiboos.com/api/delivery/statusCallback'
    
    # 京东接口返回结果Code
    ERROR_SUCCESS = 0
    ERROR_SYSTEM_ERROR = 1
    ERROR_PARAM_ERROR = 2
    ERROR_SIGN_ERROR = 3
    ERROR_UNAUTHORIZED = 4
    ERROR_RATE_LIMIT = 5
    ERROR_OTHER = 9
    ERROR_NO_CAPACITY = 10
    ERROR_ORDER_NOT_EXIST = 101
    ERROR_ORDER_COMPLETED = 102
    ERROR_SERVICE_NOT_OPEN = 103
    ERROR_OUT_OF_RANGE = 104
    ERROR_APPOINTMENT_TIME = 105
    ERROR_CAPACITY_SHORTAGE = 106
    ERROR_ACCOUNT_BALANCE = 107
    ERROR_NO_APPOINTMENT = 108
    ERROR_NO_REVERSE_ORDER = 109
    ERROR_TIP_LIMIT = 110
    ERROR_TIP_MAX = 111
    ERROR_NO_RIDER_LOCATION = 112
    ERROR_CARRIER_MISMATCH = 301
    ERROR_DELIVERY_ID_MISMATCH = 302
    ERROR_WAYBILL_NOT_EXIST = 303
    ERROR_RIDER_CODE_ERROR = 501
    ERROR_RIDER_LIMIT = 502
    ERROR_RIDER_CANNOT_ACCEPT = 503
    ERROR_FEATURE_NOT_OPEN = 504
    ERROR_STORE_CREATE_FAILED = 601
    ERROR_STORE_NO_CHANGE = 602
    
    def __init__(self, developer_id: str, secret: str, environment: str = 'test', 
                 redis_config: Optional[Dict[str, Any]] = None, 
                 enable_log: bool = True, 
                 user_agent: Optional[str] = None,
                 timeout: int = 120,
                 max_retry: int = 3):
        """
        初始化SDK
        
        Args:
            developer_id: 开发者ID
            secret: 签名密钥
            environment: 环境配置 (test, sandbox, prod)
            redis_config: Redis配置
            enable_log: 是否启用日志
            user_agent: 自定义User-Agent
            timeout: 请求超时时间（秒）
            max_retry: 最大重试次数
        """
        self.config = SDKConfig(
            developer_id=developer_id,
            secret=secret,
            environment=environment,
            redis_config=redis_config,
            enable_log=enable_log,
            user_agent=user_agent or f'JingdongCallbackSDK-Python/{self.VERSION}',
            timeout=timeout,
            max_retry=max_retry
        )
        
        # 初始化日志
        self.logger = self._init_logger()
        
        # 初始化Redis连接
        self.redis_client = self._init_redis()
        
        # 初始化HTTP会话
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': self.config.user_agent,
            'Accept': 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded'
        })
        
        self.logger.info(f"京东配送回调SDK初始化完成 - 版本: {self.VERSION}, 环境: {environment}")
    
    def _init_logger(self) -> logging.Logger:
        """初始化日志"""
        logger = logging.getLogger('jingdong_callback_sdk')
        
        if not logger.handlers and self.config.enable_log:
            handler = logging.StreamHandler()
            formatter = logging.Formatter(
                '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            )
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            logger.setLevel(logging.INFO)
        
        return logger
    
    def _init_redis(self):
        """初始化Redis连接"""
        if not self.config.redis_config:
            self.logger.info("未配置Redis，将跳过防重复检查")
            return None
        
        try:
            import redis
            
            redis_config = {
                'host': '127.0.0.1',
                'port': 6379,
                'db': 0,
                'decode_responses': True,
                'socket_timeout': 3.0,
                **self.config.redis_config
            }
            
            client = redis.Redis(**redis_config)
            # 测试连接
            client.ping()
            
            self.logger.info(f"Redis连接成功 - {redis_config['host']}:{redis_config['port']}/{redis_config['db']}")
            return client
            
        except ImportError:
            self.logger.warning("Redis模块未安装，将跳过防重复检查")
            return None
        except Exception as e:
            self.logger.error(f"Redis连接失败: {e}")
            return None
    
    def send_status_callback(self, order: Union[Order, Dict[str, Any]], status: int, 
                           extra_params: Optional[Dict[str, Any]] = None) -> CallbackResult:
        """
        发送订单状态回调
        
        Args:
            order: 订单信息（Order对象或字典）
            status: 新状态
            extra_params: 额外参数
            
        Returns:
            CallbackResult: 回调结果
        """
        try:
            # 转换订单对象
            if isinstance(order, dict):
                order_dict = order
            else:
                order_dict = self._order_to_dict(order)
            
            extra_params = extra_params or {}
            
            # 防重复机制检查
            is_transfer = extra_params.get('isTransfer', False)
            
            if not is_transfer and self.redis_client:
                deduplication_key = f"jd_callback_sent_{order_dict['id']}_{status}"
                
                if self.redis_client.exists(deduplication_key):
                    last_sent_info = self.redis_client.get(deduplication_key)
                    self.logger.info(f"京东状态回调防重复跳过 - 订单ID: {order_dict['id']}, "
                                   f"状态: {status}, 上次发送: {last_sent_info}")
                    
                    return CallbackResult(
                        success=True,
                        message='防重复跳过发送',
                        deduplicated=True
                    )
                
                # 记录本次发送
                send_info = {
                    'order_id': order_dict['id'],
                    'status': status,
                    'sent_at': time.strftime('%Y-%m-%d %H:%M:%S'),
                    'timestamp': int(time.time()),
                    'is_transfer': False
                }
                self.redis_client.setex(deduplication_key, 30, json.dumps(send_info))
            
            # 构建回调参数
            params = self._build_callback_params(order_dict, status, extra_params)
            
            # 发送回调
            response_data = self._send_callback(params)
            
            # 更新Redis缓存记录成功结果
            if self.redis_client:
                deduplication_time = 5 if is_transfer else 30
                key_prefix = "jd_transfer_sent_" if is_transfer else "jd_callback_sent_"
                deduplication_key = f"{key_prefix}{order_dict['id']}_{status}"
                
                success_info = {
                    'order_id': order_dict['id'],
                    'status': status,
                    'sent_at': time.strftime('%Y-%m-%d %H:%M:%S'),
                    'timestamp': int(time.time()),
                    'result': response_data,
                    'success': True,
                    'is_transfer': is_transfer
                }
                self.redis_client.setex(deduplication_key, deduplication_time, 
                                      json.dumps(success_info, default=str))
            
            self.logger.info(f"海博技术服务平台回调发送成功 - 订单ID: {order_dict['id']}, "
                           f"平台订单号: {order_dict.get('platform_order_no', '')}, 状态: {status}")
            
            return CallbackResult(
                success=True,
                message='回调发送成功',
                data=response_data
            )
            
        except Exception as e:
            # 清理Redis缓存，允许重试
            if self.redis_client:
                is_transfer = extra_params.get('isTransfer', False) if extra_params else False
                key_prefix = "jd_transfer_sent_" if is_transfer else "jd_callback_sent_"
                deduplication_key = f"{key_prefix}{order_dict['id']}_{status}"
                self.redis_client.delete(deduplication_key)
            
            self.logger.error(f"海博技术服务平台回调发送失败 - 订单ID: {order_dict.get('id', '')}, "
                            f"平台订单号: {order_dict.get('platform_order_no', '')}, "
                            f"状态: {status}, 错误: {e}", exc_info=True)
            
            return CallbackResult(
                success=False,
                message=f'回调发送失败: {str(e)}'
            )
    
    def _order_to_dict(self, order: Order) -> Dict[str, Any]:
        """将Order对象转换为字典"""
        result = {
            'id': order.id,
            'order_no': order.order_no,
            'platform_order_no': order.platform_order_no,
            'jd_store_id': order.jd_store_id,
            'carrier_merchant_id': order.carrier_merchant_id,
            'rider_id': order.rider_id,
            'rider_name': order.rider_name,
            'rider_phone': order.rider_phone,
            'rider_lng': order.rider_lng,
            'rider_lat': order.rider_lat,
        }
        
        if order.fee:
            result['fee'] = {
                'actual_amount': order.fee.actual_amount,
                'total_amount': order.fee.total_amount,
                'toll_fee': order.fee.toll_fee,
                'handling_fee': order.fee.handling_fee,
                'waiting_fee': order.fee.waiting_fee,
                'parking_fee': order.fee.parking_fee,
            }
        
        return result
    
    def _build_callback_params(self, order: Dict[str, Any], status: int, 
                             extra_params: Dict[str, Any]) -> Dict[str, Any]:
        """构建回调参数"""
        operate_time = int(time.time())
        
        # 业务参数
        business_params = {
            'orderId': order.get('platform_order_no') or order['order_no'],
            'carrierDeliveryId': order['order_no'],
            'carrierShopId': order.get('jd_store_id'),
            'carrierMerchantId': order.get('carrier_merchant_id', 'HBT_JZD'),
            'status': status,
            'operateTime': operate_time
        }
        
        # 根据状态添加必要的骑手信息
        rider_required_statuses = [20, 25, 30, 50]
        if status in rider_required_statuses:
            # 检查是否为转单操作
            if extra_params.get('isTransfer'):
                business_params['riderName'] = extra_params.get('newRiderName', '')
                business_params['riderPhone'] = extra_params.get('newRiderPhone', '')
                business_params['riderPhoneType'] = 0
            else:
                # 从订单信息获取骑手信息
                if order.get('rider_name'):
                    business_params['riderName'] = order['rider_name']
                if order.get('rider_phone'):
                    business_params['riderPhone'] = order['rider_phone']
                    business_params['riderPhoneType'] = 0
            
            # 处理位置信息
            if 'riderName' in business_params:
                if 'riderLng' in extra_params and 'riderLat' in extra_params:
                    business_params['riderLng'] = self._format_coordinate(extra_params['riderLng'])
                    business_params['riderLat'] = self._format_coordinate(extra_params['riderLat'])
                elif order.get('rider_lng') is not None and order.get('rider_lat') is not None:
                    business_params['riderLng'] = self._format_coordinate(order['rider_lng'])
                    business_params['riderLat'] = self._format_coordinate(order['rider_lat'])
                else:
                    # 使用默认位置（北京天安门）
                    business_params['riderLng'] = 116397470
                    business_params['riderLat'] = 39908860
                
                # 验证并调整经纬度范围
                business_params = self._validate_coordinates(business_params)
        
        # 取消状态的特殊处理
        if status == 99:
            business_params['cancelReasonCode'] = extra_params.get('cancelReasonCode', 99)
            business_params['cancelReasonDesc'] = extra_params.get('cancelReasonDesc', '其他原因取消')
            
            if 'cancelFee' in extra_params:
                business_params['cancelFee'] = float(extra_params['cancelFee'])
        
        # 费用信息
        if order.get('fee'):
            fee = order['fee']
            
            if fee.get('actual_amount'):
                business_params['actualFee'] = float(fee['actual_amount'])
            elif fee.get('total_amount'):
                business_params['actualFee'] = float(fee['total_amount'])
            
            for fee_type in ['toll_fee', 'handling_fee', 'waiting_fee', 'parking_fee']:
                if fee.get(fee_type) and fee[fee_type] > 0:
                    business_params[fee_type.replace('_', '').title() + 'Fee'] = float(fee[fee_type])
        
        # 订单图片信息
        if 'orderImages' in extra_params:
            order_images = extra_params['orderImages']
            if isinstance(order_images, str):
                business_params['orderImages'] = order_images
            else:
                business_params['orderImages'] = json.dumps(order_images, ensure_ascii=False)
        
        # 添加公共参数
        common_params = {
            'developerId': self.config.developer_id,
            'timestamp': int(time.time()),
            'version': '1.0'
        }
        
        # 合并参数
        all_params = {**common_params, **business_params}
        
        # 生成签名
        all_params['sign'] = self._generate_sign(all_params)
        
        return all_params
    
    def _generate_sign(self, params: Dict[str, Any]) -> str:
        """生成签名"""
        # 移除签名参数
        params.pop('sign', None)
        
        # 过滤空值参数
        filtered_params = {}
        for key, value in params.items():
            if value is not None and value != '' and value != []:
                filtered_params[key] = value
        
        # 按参数名字典顺序排序
        sorted_params = sorted(filtered_params.items())
        
        # 拼接参数字符串
        string_to_be_signed = ''
        for key, value in sorted_params:
            if isinstance(value, bool):
                value = 'true' if value else 'false'
            elif isinstance(value, (list, dict)):
                value = json.dumps(value, ensure_ascii=False)
            else:
                value = str(value)
            
            string_to_be_signed += key + value
        
        # 加上secret前缀
        string_to_be_signed = self.config.secret + string_to_be_signed
        
        # SHA1加密并转小写
        return hashlib.sha1(string_to_be_signed.encode('utf-8')).hexdigest().lower()
    
    def _send_callback(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """发送回调请求"""
        retry = 0
        last_error = None
        
        while retry < self.config.max_retry:
            try:
                if retry > 0:
                    self.logger.info(f"京东回调重试 - 第 {retry} 次重试 - 订单ID: {params.get('orderId')}, "
                                   f"状态: {params.get('status')}")
                
                url = self._get_callback_url()
                
                start_time = time.time()
                response = self.session.post(
                    url,
                    data=params,
                    timeout=self.config.timeout
                )
                duration = round((time.time() - start_time) * 1000, 2)
                
                self.logger.info(f"海博技术服务平台回调HTTP请求详情 - URL: {url}, "
                               f"耗时: {duration}ms, HTTP状态码: {response.status_code}, 重试: {retry}")
                
                response.raise_for_status()
                
                result = response.json()
                code = result.get('code')
                
                if code == 0:
                    if retry > 0:
                        self.logger.info(f"京东回调重试成功 - 第 {retry} 次重试成功 - "
                                       f"订单ID: {params.get('orderId')}, 状态: {params.get('status')}")
                    return result
                else:
                    error_code = code if code is not None else -1
                    error_msg = result.get('message', '未知错误')
                    
                    # 检查是否是不需要重试的错误码
                    no_retry_error_codes = [
                        self.ERROR_CARRIER_MISMATCH,
                        self.ERROR_DELIVERY_ID_MISMATCH,
                        self.ERROR_WAYBILL_NOT_EXIST
                    ]
                    
                    if error_code in no_retry_error_codes:
                        error_desc = self._get_error_description(error_code)
                        raise Exception(f'海博技术服务平台接口返回不重试错误: [{error_code}] {error_desc} - {error_msg}')
                    
                    error_desc = self._get_error_description(error_code)
                    raise Exception(f'海博技术服务平台接口返回错误: [{error_code}] {error_desc} - {error_msg}')
                
            except Exception as e:
                if '不重试错误' in str(e):
                    raise e
                
                last_error = e
                retry += 1
                
                if retry < self.config.max_retry:
                    time.sleep(retry)
        
        raise Exception(f'回调发送失败，已重试{self.config.max_retry}次: {str(last_error)}')
    
    def _get_callback_url(self) -> str:
        """获取回调URL"""
        if self.config.environment == 'prod':
            return self.CALLBACK_URL_PROD
        elif self.config.environment == 'sandbox':
            return self.CALLBACK_URL_SANDBOX
        else:
            return self.CALLBACK_URL_TEST
    
    def _format_coordinate(self, coordinate: Union[int, float]) -> int:
        """格式化坐标为京东API格式"""
        if isinstance(coordinate, (int, float)):
            # 如果已经是*10^6格式的整数，直接返回
            if abs(coordinate) > 1000000:
                return int(coordinate)
            # 否则转换为*10^6格式
            return int(coordinate * 1000000)
        return 0
    
    def _validate_coordinates(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """验证并调整坐标范围"""
        if 'riderLng' not in params or 'riderLat' not in params:
            return params
        
        rider_lng = params['riderLng']
        rider_lat = params['riderLat']
        
        # 处理负数经度
        if rider_lng < 0:
            rider_lng = 360000000 + rider_lng
        
        # 验证经度范围 (0 < lng < 360000000)
        if rider_lng <= 0 or rider_lng >= 360000000:
            rider_lng = 116397470  # 北京天安门
        
        # 验证纬度范围 (-90000000 < lat < 90000000)
        if rider_lat < -90000000 or rider_lat > 90000000:
            rider_lat = 39908860  # 北京天安门
        
        params['riderLng'] = rider_lng
        params['riderLat'] = rider_lat
        
        return params
    
    def _get_error_description(self, error_code: int) -> str:
        """获取错误码描述"""
        error_map = {
            self.ERROR_SUCCESS: '请求成功',
            self.ERROR_SYSTEM_ERROR: '系统异常',
            self.ERROR_PARAM_ERROR: '缺少参数，或参数格式错误',
            self.ERROR_SIGN_ERROR: '签名验证失败',
            self.ERROR_UNAUTHORIZED: '未授权或授权过期',
            self.ERROR_RATE_LIMIT: '接口流控',
            self.ERROR_OTHER: '其他原因',
            self.ERROR_NO_CAPACITY: '该地区暂无运力',
            self.ERROR_ORDER_NOT_EXIST: '订单不存在',
            self.ERROR_ORDER_COMPLETED: '订单已完成，不能取消',
            self.ERROR_SERVICE_NOT_OPEN: '门店未开通所选服务产品',
            self.ERROR_OUT_OF_RANGE: '送货地址超出配送范围',
            self.ERROR_APPOINTMENT_TIME: '预约时间内无法完成履约',
            self.ERROR_CAPACITY_SHORTAGE: '运力紧张，无法创建订单',
            self.ERROR_ACCOUNT_BALANCE: '账户余额不足或扣款失败',
            self.ERROR_NO_APPOINTMENT: '暂不支持预约单',
            self.ERROR_NO_REVERSE_ORDER: '暂不支持逆向发单',
            self.ERROR_TIP_LIMIT: '骑手已接单，无法添加小费',
            self.ERROR_TIP_MAX: '小费金额已至上限，无法继续添加',
            self.ERROR_NO_RIDER_LOCATION: '骑手目前没有位置信息，请稍后重试',
            self.ERROR_CARRIER_MISMATCH: '配送商不匹配',
            self.ERROR_DELIVERY_ID_MISMATCH: '配送商物流单号不匹配',
            self.ERROR_WAYBILL_NOT_EXIST: '运单不存在',
            self.ERROR_RIDER_CODE_ERROR: '骑手信息错误，请扫正确的骑手码',
            self.ERROR_RIDER_LIMIT: '骑手接单数已超上限',
            self.ERROR_RIDER_CANNOT_ACCEPT: '骑手无法接单',
            self.ERROR_FEATURE_NOT_OPEN: '请联系配送商客服开通功能后使用',
            self.ERROR_STORE_CREATE_FAILED: '暂时无法创建门店或修改门店信息，需要稍后重试',
            self.ERROR_STORE_NO_CHANGE: '此次变更未修改任何门店信息'
        }
        
        return error_map.get(error_code, f'未知错误码: {error_code}')
    
    @staticmethod
    def convert_local_status_to_jingdong_status(local_status: str) -> int:
        """将本地状态转换为京东状态"""
        status_map = {
            '1': 1,
            '10': 10,  # 待接单 => 已创建(待接单)
            '20': 20,  # 已接单 => 已接单
            '25': 25,  # 到达取货点 => 已到店
            '30': 30,  # 配送中 => 已取货
            '50': 50,  # 已完成 => 已送达
            '99': 99,  # 已取消 => 已取消
        }
        
        return status_map.get(local_status, 10)
    
    @staticmethod
    def get_cancel_reason_code(role: str) -> int:
        """根据取消角色获取京东取消原因代码"""
        reason_map = {
            'ADMIN': 1,        # 商户取消
            'CUSTOMER': 2,     # 用户取消
            'SYSTEM': 3,       # 系统取消
            'RIDER': 4,        # 骑手取消
            'CARRIER': 5,      # 配送商取消
        }
        
        return reason_map.get(role, 99)
    
    @staticmethod
    def get_cancel_reason_desc(role: str, remark: str = '') -> str:
        """根据取消角色获取取消原因描述"""
        reason_desc = {
            'ADMIN': '商户取消',
            'CUSTOMER': '用户取消',
            'SYSTEM': '系统取消',
            'RIDER': '骑手取消',
            'CARRIER': '配送商取消',
        }
        
        base_desc = reason_desc.get(role, '其他原因')
        
        if remark:
            return f'{base_desc}：{remark}'
        
        return base_desc
    
    def test_connectivity(self) -> ConnectivityResult:
        """测试连通性"""
        url = self._get_callback_url()
        parsed_url = urlparse(url)
        host = parsed_url.hostname
        port = parsed_url.port or (443 if parsed_url.scheme == 'https' else 80)
        
        # DNS解析测试
        dns_result = self._test_dns(host)
        
        # 端口连通性测试
        port_result = self._test_port(host, port)
        
        # HTTP请求测试
        http_result = self._test_http(url)
        
        return ConnectivityResult(
            dns=dns_result,
            port=port_result,
            http=http_result
        )
    
    def _test_dns(self, host: str) -> DnsResult:
        """测试DNS解析"""
        try:
            import socket
            ip = socket.gethostbyname(host)
            success = ip != host
            
            self.logger.info(f"DNS解析测试 - 主机: {host}, IP: {ip}, 成功: {success}")
            return DnsResult(host=host, ip=ip, success=success)
            
        except socket.gaierror as e:
            self.logger.error(f"DNS解析失败 - 主机: {host}, 错误: {e}")
            return DnsResult(host=host, ip='', success=False)
    
    def _test_port(self, host: str, port: int) -> PortResult:
        """测试端口连通性"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(5.0)
            result = sock.connect_ex((host, port))
            sock.close()
            
            success = result == 0
            error = None if success else f"连接错误: {result}"
            
            self.logger.info(f"端口连通性测试 - {host}:{port}, 成功: {success}")
            return PortResult(host=host, port=port, success=success, error=error)
            
        except Exception as e:
            self.logger.error(f"端口连通性测试失败 - {host}:{port}, 错误: {e}")
            return PortResult(host=host, port=port, success=False, error=str(e))
    
    def _test_http(self, url: str) -> HttpResult:
        """测试HTTP请求"""
        try:
            response = requests.head(
                url,
                headers={'User-Agent': f'JingdongCallbackSDK-ConnectivityTest/{self.VERSION}'},
                timeout=10,
                verify=False
            )
            
            success = 200 <= response.status_code < 400
            
            self.logger.info(f"HTTP请求测试 - URL: {url}, 状态码: {response.status_code}, 成功: {success}")
            return HttpResult(success=success, http_code=response.status_code)
            
        except Exception as e:
            self.logger.error(f"HTTP请求测试失败 - URL: {url}, 错误: {e}")
            return HttpResult(success=False, http_code=0, error=str(e))
    
    def close(self):
        """关闭资源"""
        if hasattr(self, 'session'):
            self.session.close()
        
        if self.redis_client:
            try:
                self.redis_client.close()
            except:
                pass
        
        self.logger.info("京东配送回调SDK资源已释放")
